/*
 * Copyright (C) 2024 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the 'License');
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an 'AS IS' BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <future>
#include "imageknife_internal.h"
#include "downsampling/down_sampler.h"
#include "file_cache.h"
#include "default_cache_key_generator.h"
#include "imageknife_internal.h"
#include "imageknife_napi.h"
#include "promise/promise_c.h"
#include "log.h"

namespace ImageKnifePro {

// static
ImageKnife &ImageKnife::GetInstance()
{
    static ImageKnifeInternal imageKnifeInternal;
    imageKnifeInternal.keyPtr_ = new DefaultCacheKeyGenerator();
    return imageKnifeInternal;
}

ImageKnifeInternal& ImageKnifeInternal::GetInstance()
{
    return static_cast<ImageKnifeInternal&>(ImageKnife::GetInstance());
}

void ImageKnifeInternal::SetRootNode(std::string id, ArkUI_NodeContentHandle handle,
                                     std::shared_ptr<ImageKnifeNodeInternal> node)
{
    IMAGE_KNIFE_CHECK(node == nullptr);
    IMAGE_KNIFE_CHECK(handle == nullptr);
    contentHandleMap_[id] = ContentHandle({.imageNode = node, .rootNode = handle});
    // 添加Native组件到NodeContent上用于挂载显示。
    OH_ArkUI_NodeContent_AddNode(handle, node->GetHandle());
}

void ImageKnifeInternal::SetImageNode(std::shared_ptr<ImageKnifeNodeInternal> node)
{
    IMAGE_KNIFE_CHECK(node == nullptr);
    static size_t count = 0;
    std::string id = "C_" + std::to_string(count);
    node->SetComponentId(id);
    contentHandleMap_[id] = ContentHandle({.imageNode = node, .rootNode = nullptr});
    count++;
}

void ImageKnifeInternal::DisposeImageNode(std::string id)
{
    if (contentHandleMap_.find(id) == contentHandleMap_.end()) {
        IMAGE_KNIFE_LOG(LOG_ERROR, "Not Found ImageNode ID:%{public}s When Dispose", id.c_str());
        return;
    }

    if (contentHandleMap_[id].rootNode != nullptr) {
        OH_ArkUI_NodeContent_RemoveNode(contentHandleMap_[id].rootNode, contentHandleMap_[id].imageNode->GetHandle());
    }
    contentHandleMap_.erase(id);
}

std::shared_ptr<ImageKnifeNodeInternal> ImageKnifeInternal::GetImageNode(std::string id)
{
    if (contentHandleMap_.find(id) != contentHandleMap_.end()) {
        return contentHandleMap_[id].imageNode;
    }

    return nullptr;
}

void ImageKnifeInternal::Execute(std::shared_ptr<ImageKnifeRequest> request)
{
    auto requestInternal = std::dynamic_pointer_cast<ImageKnifeRequestInternal>(request);
    IMAGE_KNIFE_CHECK(requestInternal == nullptr);
    if (requestInternal->MarkExecuted()) {
        return;
    }
    this->dispatcher->Enqueue(requestInternal);
}

void ImageKnifeInternal::CancelRequest(std::shared_ptr<ImageKnifeRequest> request)
{
    auto requestInternal = std::dynamic_pointer_cast<ImageKnifeRequestInternal>(request);
    IMAGE_KNIFE_CHECK(requestInternal == nullptr);
    this->dispatcher->CancelRequest(requestInternal);
}

std::shared_future<void> ImageKnifeInternal::InitFileCacheAsync(std::string cachePath,
    size_t size, size_t maxDiskUsage, std::string path)
{
    if (path.empty()) {
        path = "ImageKnife";
    }

    FileCache::GetInstance()->filePath = cachePath + "/" + path;
    FileCache::GetInstance()->SetCacheLimit(size, maxDiskUsage);

    auto promise = std::make_shared<std::promise<void>>();
    auto func = [promise]() {
        FileCache::GetInstance()->InitFileCache();
        promise->set_value();
    };

    std::thread initThread(func);
    initThread.detach();
    return promise->get_future().share();
}

void ImageKnifeInternal::InitFileCache(std::string cachePath, size_t size, size_t maxDiskUsage, std::string path)
{
    if (path.empty()) {
        path = "ImageKnife";
    }

    // 初始化文件缓存
    FileCache::GetInstance()->filePath = cachePath + "/" + path;
    FileCache::GetInstance()->SetCacheLimit(size, maxDiskUsage);

    FileCache::GetInstance()->InitFileCache();
}

void ImageKnifeInternal::SetCacheKeyGenerator(CacheKeyGenerator *keyPtr)
{
    if (keyPtr == nullptr) {
        return;
    }

    if (keyPtr_ != nullptr) {
        delete keyPtr_;
        keyPtr_ = nullptr;
    }

    keyPtr_ = keyPtr;
}

CacheKeyGenerator *ImageKnifeInternal::GetCacheKeyGenerator() const
{
    return keyPtr_;
}

void ImageKnifeInternal::SetMaxRequests(int concurrency)
{
    dispatcher->maxRequests = concurrency;
    TaskWorker::GetInstance()->SetMaxConcurrency(concurrency);
}

std::shared_ptr<ImageKnifeLoader> ImageKnifeInternal::GetDefaultImageKnifeLoader() const
{
    return imageLoader_;
}

void ImageKnifeInternal::SetDefaultImageKnifeLoader(std::shared_ptr<ImageKnifeLoader> imageLoader)
{
    imageLoader_ = imageLoader;
}

std::shared_ptr<ImageKnifeRequest> ImageKnifeInternal::Preload(std::shared_ptr<ImageKnifeOption> imageKnifeOption)
{
    auto request = std::make_shared<ImageKnifeRequestInternal>(imageKnifeOption);
    ImageKnifeInternal::GetInstance().Execute(request);
    return request;
}

void ImageKnifeInternal::SetCacheLimit(CacheStrategy cacheStrategy, size_t maxSize, size_t maxUsage)
{
    if (cacheStrategy == CacheStrategy::FILE) {
        FileCache::GetInstance()->SetCacheLimit(maxSize, maxUsage);
    } else {
        MemoryCache::GetInstance()->SetCacheLimit(maxSize, maxUsage);
    }
}

int64_t ImageKnifeInternal::GetCacheLimitSize(CacheStrategy cacheStrategy)
{
    if (cacheStrategy == CacheStrategy::FILE) {
        if (FileCache::GetInstance()->IsFileInitComplete()) {
            return FileCache::GetInstance()->GetAllowMaxDiskLimit();
        } else {
            IMAGE_KNIFE_LOG(LOG_ERROR, "File Cache Not Finish");
            return -1;
        }
    } else {
        return MemoryCache::GetInstance()->GetAllowMaxMemory();
    }
}

int64_t ImageKnifeInternal::GetCurrentCacheNum(CacheStrategy cacheStrategy)
{
    if (cacheStrategy == CacheStrategy::FILE) {
        if (FileCache::GetInstance()->IsFileInitComplete()) {
            return FileCache::GetInstance()->Size();
        } else {
            IMAGE_KNIFE_LOG(LOG_ERROR, "File Cache Not Finish");
            return -1;
        }
    } else {
        return MemoryCache::GetInstance()->Size();
    }
}

int64_t ImageKnifeInternal::GetCurrentCacheSize(CacheStrategy cacheStrategy)
{
    if (cacheStrategy == CacheStrategy::FILE) {
        if (FileCache::GetInstance()->IsFileInitComplete()) {
            return FileCache::GetInstance()->GetCurrentMemory();
        } else {
            IMAGE_KNIFE_LOG(LOG_ERROR, "File Cache Not Finish");
            return -1;
        }
    } else {
        return MemoryCache::GetInstance()->GetCurrentMemory();
    }
}

void ImageKnifeInternal::RemoveFileCache(std::string &url)
{
    if (keyPtr_ == nullptr) {
        return;
    }
    ImageSource  imageSource(url);
    std::string key = keyPtr_->GenerateFileKey(&imageSource);
    FileCache *fileCache = FileCache::GetInstance();
    if (fileCache == nullptr) {
        return;
    }

    fileCache->RemoveFileCache(key);
}

void ImageKnifeInternal::RemoveFileCache(std::shared_ptr<ImageKnifeOption> option)
{
    if (option == nullptr || keyPtr_ == nullptr) {
        return;
    }
    std::string key = keyPtr_->GenerateFileKey(&option->loadSrc, option->signature);
    FileCache *fileCache = FileCache::GetInstance();
    if (fileCache == nullptr) {
        return;
    }

    fileCache->RemoveFileCache(key);
}

void ImageKnifeInternal::RemoveAllFileCache(void)
{
    FileCache *fileCache = FileCache::GetInstance();
    if (fileCache == nullptr) {
        return;
    }

    fileCache->RemoveAllFileCache();
}

std::shared_future<void> ImageKnifeInternal::RemoveFileAsync(std::string fileName)
{
    auto promise = std::make_shared<std::promise<void>>();
    std::thread removeFileThread([this, &fileName, promise]() {
        RemoveFileCache(fileName);
        promise->set_value();
    });
    removeFileThread.detach();
    return promise->get_future().share();
}

std::shared_future<void> ImageKnifeInternal::RemoveFileAsync(std::shared_ptr<ImageKnifeOption> option)
{
    auto promise = std::make_shared<std::promise<void>>();
    std::thread removeFileThread([this, option, promise]() {
        if (option == nullptr) {
            promise->set_value();
            return;
        }

        RemoveFileCache(option);
        promise->set_value();
    });
    removeFileThread.detach();
    return promise->get_future().share();
}

std::shared_future<void> ImageKnifeInternal::RemoveAllFileAsync(void)
{
    auto promise = std::make_shared<std::promise<void>>();
    std::thread removeAllFileThread([this, promise]() {
        RemoveAllFileCache();
        promise->set_value();
    });
    removeAllFileThread.detach();
    return promise->get_future().share();
}

void ImageKnifeInternal::RemoveAllMemoryCache()
{
    auto cachePtr = MemoryCache::GetInstance();
    if (cachePtr == nullptr) {
        return;
    }

    cachePtr->MemoryCache::GetInstance()->Clear();
}

void ImageKnifeInternal::RemoveMemoryCache(std::string url)
{
    if (keyPtr_ == nullptr) {
        return;
    }

    ImageKnifeOption imageKnifeOption;
    imageKnifeOption.loadSrc.SetString(url);
    CacheKeyGenerator::MemoryKeyParams memoryKeyParams;
    memoryKeyParams.imageSource = &imageKnifeOption.loadSrc;
    memoryKeyParams.type = ImageRequestType::MAIN_SRC;
    memoryKeyParams.downSamplingInfo = DownSampler::GetDownSamplingInfo({0, 0}, DownSamplingStrategy::DEFAULT);
    std::string key = keyPtr_->GenerateMemoryKey(memoryKeyParams);
    MemoryCache::GetInstance()->Remove(key);
}

void ImageKnifeInternal::RemoveMemoryCache(std::shared_ptr<ImageKnifeOption> imageKnifeOption)
{
    if (keyPtr_ == nullptr || imageKnifeOption == nullptr) {
        return;
    }

    CacheKeyGenerator::MemoryKeyParams memoryKeyParams = {.imageSource = &imageKnifeOption->loadSrc,
                                                          .type = ImageRequestType::MAIN_SRC,
                                                          .signature = imageKnifeOption->signature};
    if (imageKnifeOption->context.config != nullptr) {
        memoryKeyParams.colorMode = imageKnifeOption->context.config->colorMode;
    }
    // 按照Default类型remove
    memoryKeyParams.downSamplingInfo = DownSampler::GetDownSamplingInfo({0, 0}, DownSamplingStrategy::DEFAULT);
    std::string key = keyPtr_->GenerateMemoryKey(memoryKeyParams);
    MemoryCache::GetInstance()->Remove(key);
}

void ImageKnifeInternal::InitImageKnifeArkTs(napi_env env, napi_value &exports)
{
    ImageKnifeNapi::Init(env, exports);
}

void ImageKnifeInternal::RegisterLoader(std::string name, std::shared_ptr<ImageKnifeLoader> loader)
{
    if (loader != nullptr) {
        loaderMap_[name] = loader;
    }
}

void ImageKnifeInternal::RegisterTransformation(std::string name, std::shared_ptr<Transformation> transformation)
{
    if (transformation != nullptr) {
        transformationMap_[name] = transformation;
    }
}

std::shared_ptr<ImageKnifeLoader> ImageKnifeInternal::GetRegisterLoader(std::string name)
{
    if (loaderMap_.find(name) == loaderMap_.end()) {
        return nullptr;
    } else {
        return loaderMap_[name];
    }
}

void ImageKnifeInternal::PutCacheImage(std::string url, std::shared_ptr<ImageData> imageData,
                                       CacheStrategy cacheType, std::string signature)
{
    IMAGE_KNIFE_CHECK(url.empty());
    IMAGE_KNIFE_CHECK(keyPtr_ == nullptr);
    IMAGE_KNIFE_CHECK(imageData == nullptr);
    ImageSource imgSource;
    imgSource.SetString(url);

    if (cacheType == CacheStrategy::DEFAULT || cacheType == CacheStrategy::MEMORY) {
        CacheKeyGenerator::MemoryKeyParams params = {.imageSource = &imgSource, .type = ImageRequestType::MAIN_SRC,
                                                     .signature = signature};
        params.downSamplingInfo = DownSampler::GetDownSamplingInfo({0, 0}, DownSamplingStrategy::DEFAULT);
        std::string memoryKey = keyPtr_->GenerateMemoryKey(params);
        if (!MemoryCache::GetInstance()->Contains(memoryKey)) {
            MemoryCache::GetInstance()->Put(memoryKey, std::make_shared<ImageDataCacheInternal>(imageData, false));
        }
    }

    if (cacheType == CacheStrategy::DEFAULT || cacheType == CacheStrategy::FILE) {
        std::string fileKey = keyPtr_->GenerateFileKey(&imgSource, signature);
        auto func = [fileKey, imageData](void *) {
            FileCache::GetInstance()->PackPixelmapToFile(fileKey, imageData->GetPixelmap());
        };
        TaskWorker::GetInstance()->PushTask(func, nullptr, nullptr);
    }
}

std::shared_ptr<Transformation> ImageKnifeInternal::GetRegisterTransformation(std::string name)
{
    if (transformationMap_.find(name) == transformationMap_.end()) {
        return nullptr;
    } else {
        return transformationMap_[name];
    }
}

void ImageKnifeInternal::Reload(std::string requestId)
{
    size_t pos = requestId.find("#");
    if (pos != std::string::npos) {
        std::string id = requestId.substr(0, pos);
        int version = atoi(requestId.substr(pos + 1).c_str());
        
        auto imageNode = GetImageNode(id);
        if (imageNode != nullptr) {
            auto request = imageNode->GetImageKnifeRequestInternal();
            IMAGE_KNIFE_CHECK(request == nullptr);
            if (request->GetVersion() == version &&
                request->GetStatus() != ImageKnifeRequest::Status::COMPLETE) {
                // 取消旧请求，避免旧请求的占位图错误图加载与新的请求产生冲突
                CancelRequest(request);
                imageNode->Reuse();
                imageNode->Update(request->GetImageKnifeOption());
                imageNode->SyncLoad(request->IsSyncLoad());
                auto newRequest = imageNode->GetImageKnifeRequestInternal();
                IMAGE_KNIFE_CHECK(newRequest == nullptr);
                IMAGE_KNIFE_LOG(LOG_DEBUG, "[ID:%{public}s VersionID:%{public}d] Request Reload",
                                id.c_str(), newRequest->GetVersion());
                Execute(newRequest);
            }
        }
    }
}

bool ImageKnifeInternal::IsFileCacheInit()
{
    return FileCache::GetInstance()->IsFileInitComplete();
}

void ImageKnifeInternal::AddHeader(std::string key, std::string value)
{
    httpHeaders_.insert(std::make_pair(key, value));
}

void ImageKnifeInternal::DeleteHeader(std::string key)
{
    httpHeaders_.erase(key);
}

const std::multimap<std::string, std::string> &ImageKnifeInternal::GetGlobalHttpHeaders()
{
    return httpHeaders_;
}

std::shared_future<std::string> ImageKnifeInternal::PreloadCache(std::shared_ptr<ImageKnifeOption> option)
{
    auto promise = std::make_shared<PromiseC<std::string>>();
    auto request = std::make_shared<ImageKnifeRequestInternal>(option, promise);
    Execute(request);
    return promise->GetFuture();
}

void ImageKnifeInternal::GetCacheImage(std::shared_ptr<Promise<std::shared_ptr<ImageData>>> promise,
    std::shared_ptr<ImageKnifeOption> option, CacheStrategy cacheType, std::string signature, bool animator)
{
    option->signature = signature;
    auto request = std::make_shared<ImageKnifeRequestInternal>(option, cacheType, promise);
    request->SetAnimator(animator);
    Execute(request);
}

std::shared_future<std::shared_ptr<ImageData>> ImageKnifeInternal::GetCacheImage(std::string loadSrc,
    CacheStrategy cacheType, std::string signature, bool animator)
{
    auto promise = std::make_shared<PromiseC<std::shared_ptr<ImageData>>>();
    auto option = std::make_shared<ImageKnifeOption>();
    option->loadSrc.SetString(loadSrc);
    GetCacheImage(promise, option, cacheType, signature, animator);
    return promise->GetFuture();
}

std::shared_future<std::shared_ptr<ImageData>> ImageKnifeInternal::GetCacheImage(
    std::shared_ptr<ImageKnifeOption> option, CacheStrategy cacheType, std::string signature, bool animator)
{
    auto promise = std::make_shared<PromiseC<std::shared_ptr<ImageData>>>();
    GetCacheImage(promise, option, cacheType, signature, animator);
    return promise->GetFuture();
}

std::shared_ptr<ImageData> ImageKnifeInternal::LoadImageSrcSync(ImageSource *imageSource, Context context,
                                                                std::string memoryKey, std::string fileKey)
{
    return dispatcher->LoadFromImageSourceSync(imageSource, context, memoryKey, fileKey);
}

void ImageKnifeInternal::SetCustomLogPrinter(LogPrinter logPrinter)
{
    ImageKnifeLogPrinter::customPrinter = logPrinter;
}

} // namespace ImageKnifePro